-- CommandM
-- Create by panyl
-- 消息指令模块

module("CommandM", package.seeall);

-- 接收到的二进制包(尚没有解析)
local dataCache = {};

-- 内部函数声明
local processMsg;

-- 消息触发器
local msgTriggers = {};

-- 回调
local msgCallback = {};

-- 所有的代理
local agents = {};

-- 模块初始化处理流程
function init()
    -- 定时循环处理通讯
    cc.Director:getInstance():getScheduler():scheduleScriptFunc(update, 0.03, false);
end

-- 网络相关的定时处理
function update()
    for _, agent in pairs(agents) do
        agent:recv();
    end
end

-- 添加代理
function addAgent(agent)
    table.insert(agents, agent);
end

-- 读取消息(newData为新读取的内容)
function analyzeMsg(agent, newData)
    local data = dataCache[agent.userName] or Buffer.create(0);
    data = Buffer.append(data, newData);

    -- 读取数据报的状态
    local WAIT_SYNC1  = 0;
    local WAIT_SYNC2  = 1;
    local WAIT_HEADER = 2;
    local WAIT_BODY   = 3;
    local readState = WAIT_SYNC1;

    -- 包头信息
    local pktHeader = {
        cmdNo = 0,
        HEADER_SIZE = 16,
    };

    -- 开始解析所有的消息
    local v;
    local offset = 1;
    while true do
        -- 没有任何数据，不再读取
        if Buffer.size(data) - offset + 1 < 1 then
            break;
        end

        -- 根据状态进行解析消息包
        if readState == WAIT_SYNC1 then
            -- 等待第一个标记：M
            v = Buffer.get8(data, offset);
            offset = offset + 1;
            if v == 77 then readState = WAIT_SYNC2 end
        elseif readState == WAIT_SYNC2 then
            -- 等待第二个标记：Z
            v = Buffer.get8(data, offset);
            offset = offset + 1;
            readState = iif(v == 90, WAIT_HEADER, WAIT_SYNC1);
        elseif readState == WAIT_HEADER then
            -- 数据不够就等待
            if Buffer.size(data) - offset + 1 < pktHeader.HEADER_SIZE - 2 then
                break;
            end

            -- 读取头部信息
            offset = offset + 2; -- checksum
            offset = offset + 4; -- tickcount
            pktHeader.msgLen = Buffer.get32(data, offset) - 2;
            offset = offset + 4;
            offset = offset + 2; -- 对齐位
            pktHeader.cmdNo = Buffer.getu16(data, offset);
            offset = offset + 2;

            if pktHeader.msgLen > 0 then
                -- 切换到等待数据包
                readState = WAIT_BODY;
            else
                -- 没有数据包了，直接处理掉
                processMsg(agent, pktHeader.cmdNo, Buffer.create(0));
                readState = WAIT_SYNC1;
            end
        else
            -- 数据不够就等待
            if Buffer.size(data) - offset + 1 < pktHeader.msgLen then
                break
            end

            -- 读取消息体
            local body = Buffer.getstr(data, offset, pktHeader.msgLen);
            offset = offset + pktHeader.msgLen;

            -- 解析并处理这个消息
            local b = Buffer.create(0);
            -- local logMsg = string.format("processMsg cmdNo:0x%04X, len:%d", pktHeader.cmdNo, #body);
            -- if pktHeader.cmdNo ~= 0xff01 then
            --     -- 心跳消息就打印了，免得太频繁
            --     cclog(logMsg);
            -- end
            Buffer.setstr(b, 1, #body, body);
            processMsg(agent, pktHeader.cmdNo, b);

            -- 切换到继续读取下一条消息的状态
            readState = WAIT_SYNC1;
        end
    end

    -- 已经解析过的废除掉
    data = Buffer.sub(data, offset, -1);
    dataCache[agent.userName] = data;
end

-- 解析并分发处理一条消息
processMsg = function(agent, cmdNo, body)
    -- 如果是心跳信息，直接处理掉
    if cmdNo == 0xff01 then
        agent:sendRaw(0xff02, body);
        return;
    end

    -- 解包
    local v = PktAnalyser.unpack(cmdNo, body);
    if not v then
        cclog("解包失败了。");
        return false;
    else
        -- 消息
        dispatch(agent, cmdNo, v);
        return true;
    end
end

function dispatch(agent, cmdNo, v)
    -- TODO: 这里不做详细的操作，因为只是给压力测试用，如果都处理了，客户端会爆掉的
    -- 压缩消息也不解压，只是告诉所有关注者说某条消息来了

    local id = iif(type(cmdNo) == "string", MSG.getMsgId(cmdNo), cmdNo);

    -- 消息触发器
    local m = msgTriggers[id] or {};
    for _, trigger in pairs(m) do
        trigger(agent, v);
    end

    -- 回调
    m = msgCallback[id] or {};
    for _, cb in pairs(m) do
        cb(agent, v);
    end
end


-- 注册消息触发器
function registerMsgTrigger(key, cmdNo, f)
    if type(f) ~= "function" then
        return;
    end

    local id = iif(type(cmdNo) == "string", MSG.getMsgId(cmdNo), cmdNo);

    msgTriggers[id] = msgTriggers[id] or {};
    msgTriggers[id][key] = f;
end


-- 注册消息回调
function registerMsgCallback(key, cmdNo, f)
    if type(f) ~= "function" then
        return;
    end

    local id = iif(type(cmdNo) == "string", MSG.getMsgId(cmdNo), cmdNo);

    msgCallback[id] = msgCallback[id] or {};
    msgCallback[id][key] = f;
end
